﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NeturalMath.Properties;

namespace NeturalMath.Units
{
    /// <summary>
    /// Built in data type to represent quantifiable units
    /// </summary>
    public sealed class UnitValue:NumberValue
    {
        #region Constructors

        internal UnitValue(double value,UnitMeasure measure,MathRuntime runtime) 
            : base(ValueTypes.Unit,"unit",value,runtime)
        {
            Measure = measure;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// The kind of measurement this unit represents
        /// </summary>
        public UnitMeasure Measure { get; private set; }

        #endregion

        #region Public Methods

        public double ToBaseline()
        {
            return Measure.ToBaseline(Value);
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (System.Object.ReferenceEquals(this, obj))
                return true;

            var other = obj as UnitValue;
            if (other == null)
                return false;

            if (!Measure.IsConvertable(other.Measure))
                return false;

            var thisVal = ToBaseline();
            var otherVal = other.ToBaseline();

            return thisVal == otherVal;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode() ^ Measure.GetHashCode();
        }

        #endregion

        #region Helper Methods

        #region Basic Math Functions

        /// <summary>
        /// Adds two values together
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Add(MathValue arg)
        {
            var other = (UnitValue)arg;
            if (!Measure.IsConvertable(other.Measure))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            var result= absThis + absOther;
            var value = Measure.FromBaseline(result);
            return Runtime.NewUnit(value, Measure);
        }

        /// <summary>
        /// Subtracts the argument from the present value
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Subtract(MathValue arg)
        {
            var other = (UnitValue)arg;
            if (!Measure.IsConvertable(other.Measure))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            var result = absThis - absOther;
            var value = Measure.FromBaseline(result);
            return Runtime.NewUnit(value, Measure);
        }

        /// <summary>
        /// Multiplies two values together
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Multiply(MathValue arg)
        {
            var other = (UnitValue)arg;

            var newMeasure = Measure*other.Measure;
            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            var result = absThis * absOther;
            var value = newMeasure.FromBaseline(result);

            return Runtime.NewUnit(value, Measure);
        }

        /// <summary>
        /// Divides two values
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Divide(MathValue arg)
        {
            var other = (UnitValue)arg;

            var newMeasure = Measure / other.Measure;
            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            var result = absThis / absOther;
            var value = newMeasure.FromBaseline(result);

            return Runtime.NewUnit(value, Measure);
        }

        /// <summary>
        /// Performs modulus division on two values
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Modulo(MathValue arg)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Raises the value to the power provided
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Power(MathValue arg)
        {
            var other = (UnitValue)arg;

            if (!other.Measure.Equals(UnitMeasure.Count))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();

            var result = Math.Pow(absThis, other.Value);
            var value = Measure.FromBaseline(result);

            return Runtime.NewUnit(value, Measure);
        }

        #endregion

        #region Logical Functions

        /// <summary>
        /// Performs an AND operation on the value
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue And(MathValue arg)
        {
            var other = (UnitValue)arg;
            if (!Measure.IsConvertable(other.Measure))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            var result = absThis.And(absOther);
            var value = Measure.FromBaseline(result);
            return Runtime.NewUnit(value, Measure);
        }

        /// <summary>
        /// Performs an OR operation on the value
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Or(MathValue arg)
        {
            var other = (UnitValue)arg;
            if (!Measure.IsConvertable(other.Measure))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            var result = absThis.Or(absOther);
            var value = Measure.FromBaseline(result);
            return Runtime.NewUnit(value, Measure);
        }

        /// <summary>
        /// Performsn an XOR operaton on the values
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Xor(MathValue arg)
        {
            var other = (UnitValue)arg;
            if (!Measure.IsConvertable(other.Measure))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            var result = absThis.Xor(absOther);
            var value = Measure.FromBaseline(result);
            return Runtime.NewUnit(value, Measure);
        }

        #endregion

        #region Relational Functions

        /// <summary>
        /// Determines if this value is less than the argument provided
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsLessThan(MathValue arg)
        {
            var other = (UnitValue)arg;
            if (!Measure.IsConvertable(other.Measure))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            return absThis < absOther;
        }

        /// <summary>
        /// Determines if the value is greater than  to the value provided
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsGreaterThan(MathValue arg)
        {
            var other = (UnitValue)arg;
            if (!Measure.IsConvertable(other.Measure))
                throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);

            var absThis = ToBaseline();
            var absOther = other.ToBaseline();

            return absThis > absOther;
        }

        #endregion

        #region Conversion Functions

        /// <summary>
        /// Attempts to convert the value to a negative
        /// </summary>
        /// <returns></returns>
        protected override MathValue ToMinus()
        {
            return Runtime.NewUnit(-Value, Measure);
        }

        
        /// <summary>
        /// Converts the current value to a string
        /// </summary>
        /// <returns>The string value of the data</returns>
        protected internal override MathValue ConvertToString()
        {
            return Runtime.NewString(GetToString());
        }

        /// <summary>
        /// Converts the argument into the present data type
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue ConvertToNativeValue(MathValue arg)
        {
            if (!(arg is UnitValue))
                return arg.ConvertToUnit();

            return arg.ConvertToUnit(Measure);
        }

        /// <summary>
        /// Converts the value to a unit, based on the specified unit measure
        /// </summary>
        /// <param name="measure"></param>
        /// <returns></returns>
        protected internal override MathValue ConvertToUnit(UnitMeasure measure)
        {
            if (Measure.IsConvertable(measure))
                return this;

            throw new MathException(ErrorCodes.UnitDimensionMismatch, Resources.UnitDimensionMismatch);
        }

        #endregion

        /// <summary>
        /// Gets a string representation of the data
        /// </summary>
        /// <returns></returns>
        protected override string GetToString()
        {
            return string.Format("{0}<{1}>",base.GetToString(),Measure);
        }

        #endregion
    }
}
